---
title: 等待通知机制
description: 如果线程要求的条件不满足时，则线程阻塞自己，进入等待状态；当线程要求的条件满足后，通知等待的线程重新执行。使用线程阻塞的方式就能避免消耗 CPU 的问题。
---

等待-通知机制是一种非常普遍的线程间协作的方式。

线程首先获取互斥锁，当线程要求的条件不满足时，释放互斥锁，进入等待状态；当要求的条件满足时，通知等待的线程，重新获取互斥锁。

## 用 synchronized 实现等待——通知机制

在 Java 中，等待——通知机制可以用多种方式实现，比如 Java 内置的 synchronized 配合 wait()、notify()、notifyAll() 就可以轻松实现。

在下图中，左边有一个等待队列，同一时刻，只允许一个线程进入synchronized 保护的临界区，当有一个线程进入临界区后，其它的线程只能进入图中左边的等待队列里等待。**这个等待队列和互斥锁是一对一的关系，每个互斥锁都有自己的等待队列。**

![c6640129fde927be8882ca90981613d0](https://img.wkq.pub/pic/c6640129fde927be8882ca90981613d0.webp)

当一个线程进入临界区后，由于某些条件不满足，需要进入条件等待队列。当调用 wait() 方法后，当前线程就会被阻塞，并且进入到右边的等待队列中，**这个等待队列也是互斥锁的等待队列**。线程在进入等待队列的同时，**会释放掉持有的互斥锁**，线程释放锁后，其它线程就有机会获得锁，然后进入临界区了。

当线程要求的条件满足时，通过 Java 对象的 notify() 和 notifyAll() 方法。如下图， 当条件满足的时候调用 notify() ,会通知等待队列（互斥锁的等待队列）中的线程，告诉它条件曾经满足过。



![1b3e999c300166a84f2e8cc7a4b8f78c](https://img.wkq.pub/pic/1b3e999c300166a84f2e8cc7a4b8f78c.webp)

曾经满足时因为**notify() 只能保证在通知时间点，条件是满足的**。而被通知的线程的执行时间点和通知的时间点基本上不会重合，所以当线程执行的时候，很可能条件已经不满足了（保不齐有其它线程插队）。

除此之外，被通知的线程要想重新执行，仍然需要获取到互斥锁。

:::tip

wait() 、notify()、notifyAll 等方法操作的等待队列是互斥锁的等待队列，所以如果 synchronized 锁定的是 this，那么对应的一定是 this.wait()、this.notify()、this.notifyAll()。

并且 wait()、notify()、notifyALl() 这三个方法能被调用的前提是已经获取到了 this 互斥锁。

最后，这三个方法都是在 synchronized{} 内部调用的，如果在 synchronized{} 外部调用，或者锁定的 this ，而用 target.wait() 调用的话，JVM 会抛出一个运行时异常：java.lang.IllegalMonitorStateException。

:::

## 使用等待通知机制优化一次性申请资源的问题
使用等待通知机制解决一次性申请转出账户和转入账户的问题。需要考虑以下四个要素。
1. 互斥锁：由于 Allocator 是单例的，所以可以使用 this 作为互斥锁。
2. 线程要求的条件：转出账户和转入账户都没有被分配过。
3. 何时等待： 线程要求的条件不满足就等待。
4. 何时通知： 当有线程释放账户就通知。

```java title='使用这种范式可以解决条件曾经满足的问题'
while(条件不满足){
    wait()
}
```
```java title='最终代码'
public class Allocator {
    private List<Object> als = new ArrayList<>();
    private static volatile Allocator instance;

    private Allocator() {
    }

    public static Allocator getInstance() {
        if (instance == null) {
            synchronized (Allocator.class) {
                if (instance == null) {
                    instance = new Allocator();
                }
            }
        }
        return instance;
    }

    //一次性申请所有资源
    synchronized void apply(Object from, Object to) {
        while (als.contains(from) || als.contains(to)) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        als.add(from);
        als.add(to);
    }
    //释放资源
    synchronized void free(Object from, Object to) {
        als.remove(from);
        als.remove(to);
        this.notifyAll();
    }
}
```
## 尽量使用 notifyAll()
notify() 是会随机地从通知等待队列中的一个线程，而 notifyAll() 会通知等待队列中的所有线程。使用 notify() 可能会导致某些线程永远不会被通知到。


除非经过深思熟虑，否则尽量使用 notifyAll()。

